package com.abevi.utils.chunker
{
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	
	/**
	 * The <code>Chunker</code> is meant to overcome the limitations for
	 * Adobe Flex/AIR when it comes to processing large loops.
	 * 	 
	 * <p>This is accomplished by breaking the processing up into smaller
	 * loops that are delayed by timers. While the timer is paused (waiting
	 * for the next iteration), the core application regains control and
	 * can continue, thereby creating the illusion of 
	 * asynchronous processing.</p> 
	 * 
	 * <p>Since the Flex/AIR architecture doesn't (currently) support
	 * threading, an application can become unresponsive as a result of
	 * being stuck in a process loop for more than a few thousand
	 * iterations.</p>
	 *
	 * @author Alex Bevilacqua	 
	 */	
	public class Chunker extends EventDispatcher
	{
		/**		  
		 * 
		 * Constructor
		 * 
		 * @param handler initialize the process loop's handler
		 * 				  function. This function should be set up to 
		 * 				  only take a single parameter, which will be
		 * 				  the current loop position.
		 */		
		public function Chunker( handler:Function=null )
		{
			super();
			
			_processFunction = handler;
			addEventListener( ChunkerProgressEvent.CANCELLED, onCancel );
		}
		
		/**
		 * The base function that will be executed during each
		 * iteration of the processLoop 
		 */		
		private var _processFunction : Function
		
		/**
		 * The number of records to process in the 
		 * loop before inserting a delay 
		 * 
		 * @default 4096
		 */		
		private var _chunkSize : uint = 4096;
		
		/**
		 * The length of the delay in milliseconds 
		 * 
		 * @default 10
		 */		
		private var _delay : uint = 10;
				
		/**
		 * The progress through the current collection
		 * 
		 * @default -1 (uninitialized) 
		 */		
		private var _position : int = -1;
		
		/**
		 * The length of the current collection 
		 */		
		private var _length : uint;				
		
		/**
		 * The number of records to read from the source
		 * object during the next iteration 
		 */		
		private var _readLength : uint;
		
		/**
		 * The internal timer used to delay processing 
		 */		
		private var _timer : Timer;
				
		public function set chunkSize( value:uint ) : void
		{
			if( _chunkSize != value ){
				_chunkSize = value;
			}
		}
		
		public function get chunkSize() : uint
		{
			return _chunkSize;
		}				
		
		public function set delay( value:uint ) : void
		{
			if( _delay != value ){
				_delay = value;
			}
		}
		
		public function get delay() : uint
		{
			return _delay;
		}		
		
		public function set length( value:uint ) : void
		{
			if( _length != value ){
				_length = value;
			}
		}
		
		public function get length() : uint
		{
			return _length;
		}
		
		/**
		 * Initialize the internal processing function.
		 *  
		 * @param value		 
		 */		
		public function setHandler( value:Function ) : void
		{
			_processFunction = value;
		}
		
		
		/**
		 * Stop procesing the current loop 		 
		 */		
		public function stop() : void
		{
			if( _timer != null ){
				fireProgressEvent( ChunkerProgressEvent.CANCELLED );	
			}else{
				throw new Error( "internal timer not initialized." );
			}
			
		}
		
				
		/**
		 * The main processing loop of the <code>Chunker</code>.		 		 
		 */		
		public function start() : void
		{
			if( _processFunction == null ){
				throw new Error( "The processFunction property must be set before any processing can occur" );
			}
			
			if( _length == -1 ){
				throw new Error( "The length property must be set before processing." );
			}			
						
			_position = 0;
			
			_timer = new Timer( _delay );
			_timer.addEventListener( TimerEvent.TIMER, processLoop );
			_timer.start();			
		}
		
		/**
		 * @private
		 * 
		 * Perform the delayed loop processing
		 *  
		 * @param e		 
		 */		
		private function processLoop( e:TimerEvent=null ) : void
		{
			// stop timer while loop executes
			_timer.stop();
			
			// set the number of records to read during
			// this iteration
			_readLength = ( _position + _chunkSize < _length ) ? _chunkSize : _length - _position;
			
			for( var i:int = 0; i < _readLength; i++ ){				
				_processFunction( _position++ );
			}									
			
			if( _position == _length ){				
				_timer.removeEventListener( TimerEvent.TIMER, processLoop );
				_timer = null;
								
				fireProgressEvent( ChunkerProgressEvent.COMPLETE );
			}else{
				fireProgressEvent( ChunkerProgressEvent.PROGRESS );
				
				_timer.start();
			}
		}
		
		/**
		 * @private
		 * 
		 * Create and dispatch a <code>ChunkerProgressEvent</code>
		 *  
		 * @param type the event enumerator to create		 
		 */		
		private function fireProgressEvent( type:String ) : void
		{
			var event : ChunkerProgressEvent = new ChunkerProgressEvent( type );
			event.position = _position;
			event.length   = _length;
			
			dispatchEvent( event );
		}				
		
		/**
		 * @private
		 * 
		 * Cancel the current loop processing and nullify the timer
		 *  
		 * @param event		 
		 */		
		private function onCancel( event:ChunkerProgressEvent ) : void
		{
			_timer.stop();
			_timer.removeEventListener( TimerEvent.TIMER, processLoop );
			_timer = null;
		}

	}
}